using System;
using System.Collections.Generic;
using System.Text;

namespace VisualEsse
{
    public static class CoordinateConverter
    {
        public const double earthRadius = 6378137;
        public const double earthCircum = earthRadius * 2.0 * Math.PI;
        public const double earthHalfCirc = earthCircum / 2;

        

        /// <summary>
        /// Converts a grid row to Latitude
        /// </summary>
        /// <param name="y"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public static float YToLatitudeAtZoom(int y, int zoom)
        {
            double arc = earthCircum / ((1 << zoom) * 256);
            double metersY = earthHalfCirc - (y * arc);
            double a = Math.Exp(metersY * 2 / earthRadius);
            double result = RadToDeg(Math.Asin((a - 1) / (a + 1)));
            return (float)result;
        }

        /// <summary>
        /// Converts a grid column to Longitude
        /// </summary>
        /// <param name="x"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public static float XToLongitudeAtZoom(int x, int zoom)
        {
            double arc = earthCircum / ((1 << zoom) * 256);
            double metersX = (x * arc) - earthHalfCirc;
            double result = RadToDeg(metersX / earthRadius);
            return (float)result;
        }

        public static int LatitudeToYAtZoom(double lat, int zoom)
        {
            double arc = earthCircum / ((1 << zoom) * 256);
            double sinLat = Math.Sin(DegToRad(lat));
            double metersY = earthRadius / 2 * Math.Log((1 + sinLat) / (1 - sinLat));
            int y = (int)Math.Round((earthHalfCirc - metersY) / arc);
            return y;
        }

        public static int LongitudeToXAtZoom(double lon, int zoom)
        {
            double arc = earthCircum / ((1 << zoom) * 256);
            double metersX = earthRadius * DegToRad(lon);
            int x = (int)Math.Round((earthHalfCirc + metersX) / arc);
            return x;
        }

        public static double Mercator(double lat)
        {
            float sinLat = (float)Math.Sin(lat * Math.PI / 180);
            return 45 * Math.Log((1 + sinLat) / (1 - sinLat))/ Math.PI;
        }

        public static double InvMercator(double y)
        {
            double e = Math.Exp(y * Math.PI / 45.0);
            return 180 * Math.Asin((e - 1) / (e + 1)) / Math.PI;
        }

        /// <summary>
        /// Converts radians to degrees
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static double RadToDeg(double d)
        {
            return d / Math.PI * 180.0;
        }

        public static double DegToRad(double d)
        {
            return d * Math.PI / 180.0;
        }

        public static double MetersPerPixel(int zoom)
        {
            double arc = earthCircum / ((1 << zoom) * 256);
            return arc;
        }
    }
}
